package com.maxlen.cart.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.maxlen.cart.constant.CartConstant;
import com.maxlen.cart.entity.to.SkuHasStockTO;
import com.maxlen.cart.entity.to.SkuInfoTO;
import com.maxlen.cart.entity.to.SkuPriceTO;
import com.maxlen.cart.feign.GoodsFeignService;
import com.maxlen.cart.feign.WareFeignService;
import com.maxlen.cart.service.CartService;
import com.maxlen.cart.entity.vo.CartVO;
import com.maxlen.common.utils.JwtUtils;
import com.maxlen.common.utils.R;
import com.maxlen.common.utils.ResultCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

@Service
public class CartServiceImpl implements CartService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    @Autowired
    private GoodsFeignService goodsFeignService;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private WareFeignService wareFeignService;

    @Override
    public R getCart(String id) {
        id = isToken(id);

        BoundHashOperations<String, Object, String> boundHashOps = stringRedisTemplate.boundHashOps(CartConstant.TEMPORARY_SHOPPING_CART + id);
        List<String> values = boundHashOps.values();
        List<CartVO.CartItemVO> cartItemVOList = new ArrayList<>();

        Integer count = 0;
        BigDecimal price = BigDecimal.valueOf(0);
        List<Long> skuIdList = new ArrayList<>();
        for (String value : values) {
            CartVO.CartItemVO cartItemVO = JSON.parseObject(value, CartVO.CartItemVO.class);
            if(cartItemVO.getCheck()){
                count += cartItemVO.getCount();
                price = price.add(cartItemVO.getTotalPrice());
            }
            cartItemVOList.add(cartItemVO);
            skuIdList.add(cartItemVO.getSkuId());
        }

        // 判断购物车所有商品是否有库存并存入 redis 中
        if(!skuIdList.isEmpty()){
            R skuHasStock = wareFeignService.getSkuHasStock(skuIdList);
            if(!skuHasStock.isOk()){
                return R.error(ResultCode.SMS_CODE_ERROR);
            }

            List<SkuHasStockTO> skuHasStockData = skuHasStock.getData(new TypeReference<List<SkuHasStockTO>>() {});
            Map<Long, Long> map = skuHasStockData.stream().collect(Collectors.toMap(SkuHasStockTO::getSkuId, SkuHasStockTO::getHasStock));
            Map<String, String> redisMap = new HashMap<>();
            for (CartVO.CartItemVO cartItemVO : cartItemVOList) {
                if(map.get(cartItemVO.getSkuId()) != null && map.get(cartItemVO.getSkuId()) >= cartItemVO.getCount()){
                    cartItemVO.setHasStock(true);
                }else{
                    cartItemVO.setCheck(false);
                    cartItemVO.setHasStock(false);
                }
                redisMap.put(cartItemVO.getSkuId().toString(), JSON.toJSONString(cartItemVO));
            }
            boundHashOps.putAll(redisMap);
        }


        CartVO cartVO = new CartVO();
        int i = 0;
        int j = cartItemVOList.size() - 1;
        while(i < j){
            if(cartItemVOList.get(i).getHasStock()){
                i++;
            }
            if(!cartItemVOList.get(j).getHasStock()){
               j--;
            }
            if(!cartItemVOList.get(i).getHasStock() && cartItemVOList.get(j).getHasStock() && i < j){
                CartVO.CartItemVO cartItemVO = cartItemVOList.get(i);
                cartItemVOList.set(i, cartItemVOList.get(j));
                cartItemVOList.set(j, cartItemVO);
            }
        }
        cartVO.setItems(cartItemVOList);
        cartVO.setCountType(cartItemVOList.size());
        cartVO.setCountNum(count);
        cartVO.setTotalAmount(price);

        return R.ok().put("data", cartVO);
    }

    /**
     * 获取购物车信息
     * @param id
     * @return
     */
    @Override
    public List<CartVO.CartItemVO> getCartItem(String id, String token) {

        String tokenId = isToken(token);
        if(StringUtils.hasText(tokenId)){
            if(StringUtils.hasText(id)){
                BoundHashOperations<String, Object, String> boundHashOps = stringRedisTemplate.boundHashOps(CartConstant.TEMPORARY_SHOPPING_CART + id);
                List<String> values = boundHashOps.values();
                for (String value : values) {
                    CartVO.CartItemVO cartItemVO = JSON.parseObject(value, CartVO.CartItemVO.class);
                    setCartItem(cartItemVO.getSkuId(), tokenId, cartItemVO.getCount());
                }
                stringRedisTemplate.delete(CartConstant.TEMPORARY_SHOPPING_CART + id);
            }
            return getCartItemVO(tokenId);
        }else{
            if(StringUtils.hasText(id)){
                return getCartItemVO(id);
            }
        }

        return new ArrayList<>();
    }

    @Override
    public R setCartItem(Long skuId, String id, Integer count) {

        id = isToken(id);

        BoundHashOperations<String, Object, Object> boundHashOps = stringRedisTemplate.boundHashOps(CartConstant.TEMPORARY_SHOPPING_CART + id);
        String result = (String) boundHashOps.get(skuId.toString());

        if(StringUtils.hasText(result)){

            CartVO.CartItemVO cartItemVO = JSON.parseObject(result, CartVO.CartItemVO.class);
            if(count != null){
                cartItemVO.setCount(cartItemVO.getCount() + count);
            }else{
                cartItemVO.setCount(cartItemVO.getCount() + 1);
            }
            addAttr(cartItemVO);
            boundHashOps.put(skuId.toString(), JSON.toJSONString(cartItemVO));

            return R.ok().put("data", cartItemVO);

        }else{

            try {
                CartVO.CartItemVO cartItemVO = new CartVO.CartItemVO();

                CompletableFuture<Void> skuInfoFuture = CompletableFuture.runAsync(() -> {
                    R serviceInfo = goodsFeignService.getInfo(skuId);
                    if (serviceInfo.isOk()) {
                        SkuInfoTO skuInfoTO = serviceInfo.getData(new TypeReference<SkuInfoTO>() {
                        });
                        cartItemVO.setCheck(true);
                        cartItemVO.setSkuId(skuId);
                        cartItemVO.setSkuImg(skuInfoTO.getSkuDefaultImg());
                        cartItemVO.setSkuTitle(skuInfoTO.getSkuTitle());
                        cartItemVO.setSkuPrice(skuInfoTO.getPrice());
                        cartItemVO.setCount(count != null ? count : 1);
                        addAttr(cartItemVO);
                    }else{
                        throw new RuntimeException();
                    }
                }, threadPoolExecutor);

                CompletableFuture<Void> skuAttrFuture = CompletableFuture.runAsync(() -> {
                    R saleAttr = goodsFeignService.getSaleAttr(skuId);
                    if (saleAttr.isOk()){
                        List<String> saleAttrData = saleAttr.getData(new TypeReference<List<String>>() {});
                        cartItemVO.setSkuAttr(saleAttrData);
                    }else{
                        throw new RuntimeException();
                    }
                }, threadPoolExecutor);

                CompletableFuture<Void> spuWeightFuture = CompletableFuture.runAsync(() -> {
                    R weight = goodsFeignService.getWeight(skuId);
                    if (weight.isOk()){
                        BigDecimal weightData = weight.getData(new TypeReference<BigDecimal>() {});
                        cartItemVO.setWeight(weightData);
                    }else{
                        throw new RuntimeException();
                    }
                }, threadPoolExecutor);

                CompletableFuture.allOf(skuInfoFuture, skuAttrFuture, spuWeightFuture).get();

                boundHashOps.put(skuId.toString(), JSON.toJSONString(cartItemVO));

                return R.ok().put("data", cartItemVO);

            } catch (InterruptedException e) {
                return R.error();
            } catch (ExecutionException e) {
                return R.error();
            }
        }
    }

    @Override
    public R setCartItemCount(String id, Long skuId, Integer count) {
        id = isToken(id);

        BoundHashOperations<String, Object, Object> boundHashOps = stringRedisTemplate.boundHashOps(CartConstant.TEMPORARY_SHOPPING_CART + id);
        String result = (String) boundHashOps.get(skuId.toString());

        CartVO.CartItemVO cartItemVO = JSON.parseObject(result, CartVO.CartItemVO.class);
        cartItemVO.setCount(count);
        addAttr(cartItemVO);
        boundHashOps.put(skuId.toString(), JSON.toJSONString(cartItemVO));

        return R.ok();

    }

    @Override
    public R delCartItem(String id, Long skuId) {
        id = isToken(id);

        BoundHashOperations<String, Object, Object> boundHashOps = stringRedisTemplate.boundHashOps(CartConstant.TEMPORARY_SHOPPING_CART + id);
        boundHashOps.delete(skuId.toString());

        return R.ok();

    }

    @Override
    public R batchDelCartItem(String id, List<String> skuId) {
        id = isToken(id);

        BoundHashOperations<String, Object, String> boundHashOps = stringRedisTemplate.boundHashOps(CartConstant.TEMPORARY_SHOPPING_CART + id);
        if(!skuId.isEmpty()){
            boundHashOps.delete(skuId.toArray());
        }
        return R.ok();
    }

    @Override
    public R getCurrentUserCartItemByCheck(Long id) {
        BoundHashOperations<String, Object, String> boundHashOps = stringRedisTemplate.boundHashOps(CartConstant.TEMPORARY_SHOPPING_CART + id);
        List<String> values = boundHashOps.values();
        List<CartVO.CartItemVO> cartItemVOList = new ArrayList<>();
        List<Long> skuIdList = new ArrayList<>();

        for (String value : values) {
            CartVO.CartItemVO cartItemVO = JSON.parseObject(value, CartVO.CartItemVO.class);
            if(cartItemVO.getCheck()){
                cartItemVOList.add(cartItemVO);
                skuIdList.add(cartItemVO.getSkuId());
            }
        }

        return changeReallyPrice(cartItemVOList, skuIdList);
    }

    @Override
    public R getCurrentUserCartItemByCheckWithHasStock(Long id) {

        BoundHashOperations<String, Object, String> boundHashOps = stringRedisTemplate.boundHashOps(CartConstant.TEMPORARY_SHOPPING_CART + id);
        List<String> values = boundHashOps.values();
        List<CartVO.CartItemVO> cartItemVOList = new ArrayList<>();
        List<Long> skuIdList = new ArrayList<>();

        for (String value : values) {
            CartVO.CartItemVO cartItemVO = JSON.parseObject(value, CartVO.CartItemVO.class);
            if(cartItemVO.getCheck()){
                cartItemVOList.add(cartItemVO);
                skuIdList.add(cartItemVO.getSkuId());
            }
        }

        R skuHasStock = wareFeignService.getSkuHasStock(skuIdList);
        if(!skuHasStock.isOk()){
            return R.error();
        }

        List<SkuHasStockTO> skuHasStockData = skuHasStock.getData(new TypeReference<List<SkuHasStockTO>>() {});
        Map<Long, Long> map = skuHasStockData.stream().collect(Collectors.toMap(SkuHasStockTO::getSkuId, SkuHasStockTO::getHasStock));
        List<CartVO.CartItemVO> cartItemVOS = new ArrayList<>();
        for (CartVO.CartItemVO item : cartItemVOList) {
            if(map.get(item.getSkuId()) != null && map.get(item.getSkuId()) > 0L){
                cartItemVOS.add(item);
            }
        }

        List<Long> longList = cartItemVOS.stream().map((item) -> {
            return item.getSkuId();
        }).collect(Collectors.toList());

        return changeReallyPrice(cartItemVOS, longList);
    }

    @Override
    public R batchDelCartItemByMember(String memberId, List<String> skuId) {

        BoundHashOperations<String, Object, String> boundHashOps = stringRedisTemplate.boundHashOps(CartConstant.TEMPORARY_SHOPPING_CART + memberId);
        if(!skuId.isEmpty()){
            boundHashOps.delete(skuId.toArray());
        }
        return R.ok();

    }

    @Override
    public R isCheckTrue(String id) {
        id = isToken(id);

        BoundHashOperations<String, Object, String> boundHashOps = stringRedisTemplate.boundHashOps(CartConstant.TEMPORARY_SHOPPING_CART + id);
        List<String> values = boundHashOps.values();

        for (String value : values) {
            CartVO.CartItemVO cartItemVO = JSON.parseObject(value, CartVO.CartItemVO.class);
            if(cartItemVO.getCheck() && cartItemVO.getHasStock()){
                return R.ok();
            }
        }

        return R.error(ResultCode.CHECK_ONE_TRUE);
    }

    /**
     * 查询数据库更改成真正的价格
     * @return
     */
    private R changeReallyPrice(List<CartVO.CartItemVO> cartItemVOList, List<Long> skuIdList){
        R priceBySkuIdList = goodsFeignService.getPriceBySkuIdList(skuIdList);
        if(priceBySkuIdList.isOk()){
            List<CartVO.CartItemVO> cartItemVOListTrue = new ArrayList<>();
            List<SkuPriceTO> skuPriceTOList = priceBySkuIdList.getData(new TypeReference<List<SkuPriceTO>>() {});
            for (CartVO.CartItemVO cartItemVO : cartItemVOList) {
                boolean isPrice = false;
                for (SkuPriceTO skuPriceTO : skuPriceTOList) {
                    if(skuPriceTO.getSkuId().equals(cartItemVO.getSkuId())){
                        isPrice = true;
                        cartItemVO.setSkuPrice(skuPriceTO.getPrice());
                        break;
                    }
                }
                if(isPrice){
                    addAttr(cartItemVO);
                    cartItemVOListTrue.add(cartItemVO);
                }
            }
            return R.ok().setData(cartItemVOListTrue);
        }

        return R.error();
    }

    @Override
    public R setCartItemCheckAll(String id, Boolean check) {
        id = isToken(id);

        BoundHashOperations<String, Object, String> boundHashOps = stringRedisTemplate.boundHashOps(CartConstant.TEMPORARY_SHOPPING_CART + id);
        List<String> values = boundHashOps.values();
        Map<String, String> map = new HashMap<>();

        for (String value : values) {
            CartVO.CartItemVO cartItemVO = JSON.parseObject(value, CartVO.CartItemVO.class);
            cartItemVO.setCheck(check);
            map.put(cartItemVO.getSkuId().toString(), JSON.toJSONString(cartItemVO));
        }

        boundHashOps.putAll(map);

        return R.ok();

    }

    @Override
    public R setCartItemCheck(String id, Long skuId) {
        id = isToken(id);

        BoundHashOperations<String, Object, Object> boundHashOps = stringRedisTemplate.boundHashOps(CartConstant.TEMPORARY_SHOPPING_CART + id);
        String result = (String) boundHashOps.get(skuId.toString());

        CartVO.CartItemVO cartItemVO = JSON.parseObject(result, CartVO.CartItemVO.class);
        cartItemVO.setCheck(!cartItemVO.getCheck());
        boundHashOps.put(skuId.toString(), JSON.toJSONString(cartItemVO));

        return R.ok();

    }

    private void addAttr(CartVO.CartItemVO cartItemVO){
        cartItemVO.setTotalPrice(new BigDecimal(cartItemVO.getCount()).multiply(cartItemVO.getSkuPrice()));
    }

    private String isToken(String id){
        try{
            if(JwtUtils.checkToken(id)){
                return JwtUtils.getMemberIdByJwtToken(id);
            }
            return id;
        }catch (Exception e){
            return null;
        }
    }

    private List<CartVO.CartItemVO> getCartItemVO(String id){

        BoundHashOperations<String, Object, String> boundHashOps = stringRedisTemplate.boundHashOps(CartConstant.TEMPORARY_SHOPPING_CART + id);
        List<String> values = boundHashOps.values();
        List<CartVO.CartItemVO> cartItemVOList = new ArrayList<>();

        for (String value : values) {
            CartVO.CartItemVO cartItemVO = JSON.parseObject(value, CartVO.CartItemVO.class);
            cartItemVOList.add(cartItemVO);
        }

        return cartItemVOList;
    }
}
